Skip to content

Add maxflow #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Sep 13, 2020
Merged

Add maxflow #24

merged 17 commits into from
Sep 13, 2020

Conversation

TonalidadeHidrica
Copy link
Collaborator

No description provided.

@TonalidadeHidrica TonalidadeHidrica marked this pull request as draft September 8, 2020 15:20
@TonalidadeHidrica TonalidadeHidrica changed the title Adding maxflow Add maxflow Sep 8, 2020
@TonalidadeHidrica TonalidadeHidrica marked this pull request as ready for review September 8, 2020 18:07
@TonalidadeHidrica
Copy link
Collaborator Author

I will add tests and an example code that is submittable to D - Maxflow, but I will open the PR now. Waiting everyone for review!

if res == up {
break;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
}
self.iter[v] = self.graph.g[v].len();

This is an important bit of the algorithm that we should not omit. Alternatively we can do

self.level[v] = self.graph.g.len();

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. It is difficult to translate C++ for-statements with side effects...

src/maxflow.rs Outdated
pub fn add_edge(&mut self, from: usize, to: usize, cap: Cap) -> usize {
assert!(from < self._n);
assert!(to < self._n);
assert!(Cap::zero() < cap);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert!(Cap::zero() < cap);
assert!(Cap::zero() <= cap);

src/maxflow.rs Outdated
pub fn add_edge(&mut self, from: usize, to: usize, cap: Cap) -> usize {
assert!(from < self._n);
assert!(to < self._n);
assert!(Cap::zero() < cap);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert!(Cap::zero() < cap);
assert!(Cap::zero() < cap);
assert_ne!(from, to);

or fix rev calculation please.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you teach me why do we need this? The original documentation says that we can input self-loops to graphs, and I don't think that the code does not work for the situation. What change do I have to apply to rev calculation?

Copy link
Contributor

@MiSawa MiSawa Sep 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original implementation has the same bug-ish behavior although both this and the original impl technically "work" as these edges won't be touched... I believe. Though it is better to fix, since this bug brakes a fundamental invariant of this graph structure.

The problem is that; when from == to, rev calculated in L:68 will be the index of the edge added in L:69, i.e. itself instead of the reverse edge.
Since rev should refer the index of the reversal auxiliary edge, it has to be calculated by

 let rev = self.g[to].len() + if from == to { 1 } else { 0 };

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I've understood. Fixing.

src/maxflow.rs Outdated
fn change_edge(&mut self, i: usize, new_cap: Cap, new_flow: Cap) {
let m = self.pos.len();
assert!(i < m);
assert!(Cap::zero() < new_flow && new_flow <= new_cap);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert!(Cap::zero() < new_flow && new_flow <= new_cap);
assert!(Cap::zero() <= new_flow && new_flow <= new_cap);

pub fn flow(&mut self, s: usize, t: usize) -> Cap {
self.flow_with_capacity(s, t, Cap::max_value())
}
pub fn flow_with_capacity(&mut self, s: usize, t: usize, flow_limit: Cap) -> Cap {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason of this naming? It make sense to name this a capacity in some specific context, but here probably doesn't. Is there any reference that this is called capacity?
Maybe flow_with_limit, flow_with_value_limit or something like that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have an image of adding new edge from virtual vertex s' to vertex s with capacity flow_limit. But I agree that flow_with_limit may be more intuitive. I want to ask others' opinion for this point.

pub fn flow_with_capacity(&mut self, s: usize, t: usize, flow_limit: Cap) -> Cap {
let n_ = self._n;
assert!(s < n_);
assert!(t < n_);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert!(t < n_);
assert!(t < n_);
assert_ne!(s, t);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I should not add this assertion, because the original documentation does not say it disallows the same vertices, and by definition we can return a valid value flow_limit.

Copy link
Contributor

@MiSawa MiSawa Sep 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By definition in the document appendix.html, we should return 0 I believe, which is very confusing as you might have noticed.

g(v, f) = Sum{...} - Sum{...}

This implies the sum of g(v, f) over all vertices is zero.

g(v, f) = g(v, f') holds for all vertices v other than s and t.

Since s = t, this also implies g(t, f) = g(t, f'). (∵ everywhere except t is fixed, and the sum is fixed)

g(t,f′)−g(t,f) is maximized under these conditions. It returns this g(t,f′)−g(t,f)

Since g(t, f) = g(t, f'), the returned value should be 0.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I finally understood. It seems to be true that it should return 0. Doesn't the current code?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried the original library with a simple example and it returned MAX_VALUE instead...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't return 0 if the limit wasn't 0, since s is reachable from itself, and v == self.s holds in the first call of dfs.

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test() {
        let mut g = MfGraph::new(3);
        assert_eq!(0, g.flow_with_capacity(0, 0, 10));
    }
}

fails with

Left:  0
Right: 10

If you formulate the max flow problem in LP, the primal would be UNBOUND and the dual would be INFEASIBLE. So it also makes sense to return the infinity.

Since there are multiple way of interpreting the meaning of max flow for s == t case, I'd just add assert_ne.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reported in atcoder/ac-library#5

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACL fixed it :)

src/maxflow.rs Outdated
fn max_value() -> Self;
}

impl MfCapacity for i32 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use macro for these impls?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a preliminary implementation. The internal_type_traits implementation (#19) provides with more wide variety of features, so I will replace with it once it is merged.

self.pos = 0;
}

pub(crate) fn pop(&mut self) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe fn pop(&mut self) -> Option<&T>?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is an enhancement that is not in the original library, but it seems useful and safe, so I'll add it. The discussion about internal_queue is underway in #13, by the way.

pub fn flow_with_capacity(&mut self, s: usize, t: usize, flow_limit: Cap) -> Cap {
let n_ = self._n;
assert!(s < n_);
assert!(t < n_);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't return 0 if the limit wasn't 0, since s is reachable from itself, and v == self.s holds in the first call of dfs.

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test() {
        let mut g = MfGraph::new(3);
        assert_eq!(0, g.flow_with_capacity(0, 0, 10));
    }
}

fails with

Left:  0
Right: 10

If you formulate the max flow problem in LP, the primal would be UNBOUND and the dual would be INFEASIBLE. So it also makes sense to return the infinity.

Since there are multiple way of interpreting the meaning of max flow for s == t case, I'd just add assert_ne.

pub fn flow_with_capacity(&mut self, s: usize, t: usize, flow_limit: Cap) -> Cap {
let n_ = self._n;
assert!(s < n_);
assert!(t < n_);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plus

Suggested change
assert!(t < n_);
assert!(t < n_);
assert!(Cap::zero() <= flow_limit);

maybe?

pub fn flow_with_capacity(&mut self, s: usize, t: usize, flow_limit: Cap) -> Cap {
let n_ = self._n;
assert!(s < n_);
assert!(t < n_);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reported in atcoder/ac-library#5

@TonalidadeHidrica
Copy link
Collaborator Author

@MiSawa Sorry to be late. I think I added all the assertions you mentioned and fixed all the mistakes. Now I'm going to add some tests. Do you have any good idea of test graphs?

@MiSawa
Copy link
Contributor

MiSawa commented Sep 11, 2020

@MiSawa Sorry to be late. I think I added all the assertions you mentioned and fixed all the mistakes. Now I'm going to add some tests. Do you have any good idea of test graphs?

Thank you for being patient about all my nitpicks!

  • This example in Wikipedia has unique optimal solution and very simple. Maybe good as the first test.
  • Here's unit tests for the original library.
  • Here's a test case for a common mistake of Dinic algorithm's implementations, although I know the current version is fine.

Copy link
Contributor

@MiSawa MiSawa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM except for missing tests :)


impl<Cap> MfGraph<Cap>
where
Cap: Integral,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually the algorithm doesn't rely on the integrality.... Though it does rely on Ord. Not sure what we should do.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One possible way is to define a trait dedicated for max-flow capacities, which is my previous implementation. Refer to this commit to see the differences. However this is a bit like a repetition of Integral trait in internal_type_traits module, that's why I simply reused it.
At least the original library guarantees only int and ll, I guess this is already sufficient.

By the way, can this implementation be applied to floating-point capacity? I mean, don't we need some additional treatment of precision errors?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question!
I believe so long as min(x, y) in {x, y} and x-x = 0, the algorithm work fine-ish. e.g. for floating point numbers without NaN / inf, polynomial, etc.
I said fine-ish since although the time complexity doesn't get worse, there are errors that are unavoidable without using multi-precision floating point number. Guaranteeing the accuracy of the returned flow value and flows on edges would be hard. Farthemore since cut is discrete, the min-cut verticis can be different from the correct one although the cut value should be similar.

In competitive programming, I think I have seen some problems that uses polynomial flows (or big-M instead) and floating point numbers (came from binary/ternary search, can be transformed to a search on rational numbers though require a lot more coding).
Though maybe it will be very rare in AtCoder at least, since ACL doesn't support them.

@TonalidadeHidrica
Copy link
Collaborator Author

About the tests:

  • Graphs from Wikipedia is licensed under CC-BY-SA 3.0, so we may use them as long as we mention the reference.
  • The license for test cases of original libraries is still yet to be determined, so I'll ask the author if I can use it.
  • @MiSawa Under which license can I use your test case?

(I'm not a lawyer, I'm not confident at all.. After all, do I even have to consider the licenses about those small graphs?)

@MiSawa
Copy link
Contributor

MiSawa commented Sep 11, 2020

@TonalidadeHidrica

* Graphs from Wikipedia is licensed under CC-BY-SA 3.0, so we may use them as long as we mention the reference.

I think CC-BY-SA 3.0 applies to the picture itself, not to the graph instance that is represented by the picture. Though not really sure, and it's safer to mention the origin anyway.

* @MiSawa Under which license can I use your test case?

NYSL! Added a comment in the gist :)

@TonalidadeHidrica
Copy link
Collaborator Author

TonalidadeHidrica commented Sep 11, 2020

Why does clippy emit warnings in some platforms but not all?? It's not because of platform but the version.


#[test]
#[allow(clippy::many_single_char_names)]
fn test_max_flow_misawa() {
Copy link
Contributor

@MiSawa MiSawa Sep 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might have done already, but verified locally that it gonna take very long with n=20 and take forever with n=100 when we deleted L:189 and L:209 as intended :)

@TonalidadeHidrica
Copy link
Collaborator Author

@qryxip Maybe we can merge this already.

@qryxip qryxip merged commit 5103743 into rust-lang-ja:master Sep 13, 2020
@TonalidadeHidrica TonalidadeHidrica deleted the feature/maxflow branch September 13, 2020 04:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants